# 
# Place 'requires_oauth' declaration in controller to protect the actions
# with generated OAuth provider
# 
# e.g.
#   ./script/generate oauth_provider SampleProvider
# 
#   class ApisController < ApplicationController
#     # a before_filter for your actions
#     requires_oauth :from => SampleProviderInstance
#
#     def get_friends
#       # Once inside, @oauth_instance of SampleProviderInstance class 
#       # would've been set for you
#       logger.info "Authenticated with #{@oauth_instance.access_token}"
#
#       # You can obtain your associated User model via oauth_user()
#       # Read "TODO" in your oauth_provider controller.authorize()
#       # on creating association between SampleProviderInstance & User 
#       # models
#       user = @oauth_instance.oauth_user
#       render :xml => user.friends.to_xml
#     end
#
#   end
# 
module ActionController
  module Filters
    module RequiresOAuth
      class OAuthExceptionBase < Exception; end
      class InvalidToken < OAuthExceptionBase; end
      class InvalidSignature < OAuthExceptionBase; end
      
      def self.included(base)
        base.extend ClassMethods
      end

      module ClassMethods
        def requires_oauth(opts = {})
          return if self.included_modules.include?(ActionController::Filters::RequiresOAuth::ActMethods)

          class_eval do
            include ActionController::Filters::RequiresOAuth::ActMethods
            before_filter :verify_oauth_request
            def self.requires_oauth_opts
              @requires_oauth_opts
            end
          end
          @requires_oauth_opts = opts
        end
      end
    
      module ActMethods
        def oauth_provider
          @oauth_provider
        end

        def oauth_instance
          @oauth_instance
        end
        
        def verify_oauth_request
          verify_oauth_signature && oauth_instance
        end

        def verify_oauth_signature
          klass = self.class.requires_oauth_opts[:from].kind_of?(Class) ?
            self.class.requires_oauth_opts[:from] :
            self.class.requires_oauth_opts[:from].to_s.camelize.constantize
          instance_klass = "#{klass}Instance".constantize
          self_klass_ref = klass.name.tableize.singularize
          
          logger.debug "params: #{request.params.to_yaml}"
          valid = OAuth::Signature.verify(request) do |token|
            @oauth_instance = instance_klass.find_by_request_token_and_access_token(nil, token.token)
            @oauth_provider = @oauth_instance.send(self_klass_ref)

            # return the token secret and the consumer secret
            logger.debug "verifying #{[@oauth_instance.access_secret, @oauth_provider.consumer_secret].inspect}"
            [@oauth_instance.access_secret, @oauth_provider.consumer_secret]
          end
          logger.debug "verified as #{valid}"
          render :text => "Invalid OAuth Request", :status => 401 unless valid
          valid
        end
      end
    end
  end
end

ActionController::Base.class_eval { include ActionController::Filters::RequiresOAuth }